// File: jmcomic-core/src/main/java/dev/jukomu/common/util/file/FileUtils.java
package dev.jukomu.common.util.file;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

public class FileUtils {

    private static final char[] WIN_FORBID_CHARS = "\\/:*?\"<>|\n\t\r".toCharArray();

    /**
     * 将文件名中的非法字符替换为指定字符，使其符合Windows文件命名规范。
     *
     * @param filename 原始文件名
     * @param attrChar 替换非法字符的字符
     * @return 合法的文件名
     */
    public static String fixWinDirName(String filename, char attrChar) {
        if (filename == null || filename.isEmpty()) {
            return filename;
        }
        StringBuilder sb = new StringBuilder(filename.length());
        for (char c : filename.toCharArray()) {
            boolean isForbidden = false;
            for (char forbiddenChar : WIN_FORBID_CHARS) {
                if (c == forbiddenChar) {
                    isForbidden = true;
                    break;
                }
            }
            sb.append(isForbidden ? attrChar : c);
        }
        return sb.toString();
    }

    /**
     * 将文件名中的非法字符替换为下划线，使其符合Windows文件命名规范。
     *
     * @param filename 原始文件名
     * @return 合法的文件名
     */
    public static String fixWinDirName(String filename) {
        return fixWinDirName(filename, '_');
    }

    /**
     * 统一文件路径为Unix风格，并确保目录以斜杠结尾，文件不以斜杠结尾。
     *
     * @param filepath 文件路径
     * @return 统一后的文件路径
     */
    public static String fixFilepath(String filepath) {
        if (filepath == null || filepath.isEmpty()) {
            return filepath;
        }
        String fixed = filepath.replace("\\", "/").replaceAll("//", "/");
        Path path = Paths.get(fixed);
        if (Files.isDirectory(path)) {
            return fixed.endsWith("/") ? fixed : fixed + "/";
        } else {
            return fixed;
        }
    }

    /**
     * 确保文件后缀以"."开头。
     *
     * @param suffix 后缀名
     * @return 确保以"."开头的后缀名
     */
    public static String fixSuffix(String suffix) {
        if (suffix == null || suffix.isEmpty()) {
            return "";
        }
        return suffix.startsWith(".") ? suffix : "." + suffix;
    }

    /**
     * 如果目录不存在则创建。
     *
     * @param dirpath 目录路径
     * @throws IOException 如果创建目录失败
     */
    public static void mkdirIfNotExists(String dirpath) throws IOException {
        Path path = Paths.get(dirpath);
        if (!Files.exists(path)) {
            Files.createDirectories(path);
        }
    }

    /**
     * 检查文件是否存在。
     *
     * @param filepath 文件路径
     * @return 如果文件存在返回 true
     */
    public static boolean fileExists(String filepath) {
        return Files.exists(Paths.get(filepath));
    }

    /**
     * 检查文件是否不存在。
     *
     * @param filepath 文件路径
     * @return 如果文件不存在返回 true
     */
    public static boolean fileNotExists(String filepath) {
        return !fileExists(filepath);
    }

    /**
     * 获取路径中最后一个文件分隔符的索引。
     *
     * @param filepath 文件路径
     * @return 索引
     */
    public static int pathLastSeparatorIndex(String filepath) {
        return Math.max(filepath.lastIndexOf("/"), filepath.lastIndexOf("\\"));
    }

    /**
     * 从文件路径中获取文件名（不包含路径）。
     *
     * @param filepath   文件路径
     * @param trimSuffix 是否去除后缀
     * @return 文件名
     */
    public static String getFileName(String filepath, boolean trimSuffix) {
        String name = filepath.substring(pathLastSeparatorIndex(filepath) + 1);
        if (trimSuffix) {
            int dotIndex = name.lastIndexOf(".");
            if (dotIndex != -1) {
                return name.substring(0, dotIndex);
            }
        }
        return name;
    }

    /**
     * 从文件路径中获取文件名（不包含路径且不去除后缀）。
     *
     * @param filepath 文件路径
     * @return 文件名
     */
    public static String getFileName(String filepath) {
        return getFileName(filepath, false);
    }

    /**
     * 从文件路径中获取文件后缀。
     *
     * @param filepath 文件路径
     * @param trimDot  是否去除开头的点
     * @return 文件后缀
     */
    public static String getFileSuffix(String filepath, boolean trimDot) {
        int dotIndex = filepath.lastIndexOf(".");
        if (dotIndex == -1) {
            return "";
        }
        return filepath.substring(dotIndex + (trimDot ? 1 : 0));
    }

    /**
     * 检查两个文件路径的后缀是否相等。
     *
     * @param fp1 文件路径1
     * @param fp2 文件路径2
     * @return 如果后缀相等返回 true
     */
    public static boolean suffixEqual(String fp1, String fp2) {
        return getFileSuffix(fp1, false).equalsIgnoreCase(getFileSuffix(fp2, false));
    }

    /**
     * 检查两个文件路径的后缀是否不相等。
     *
     * @param fp1 文件路径1
     * @param fp2 文件路径2
     * @return 如果后缀不相等返回 true
     */
    public static boolean suffixNotEqual(String fp1, String fp2) {
        return !suffixEqual(fp1, fp2);
    }

    /**
     * 更改文件路径中的文件名部分。
     *
     * @param filepath 原始文件路径
     * @param newName  新的文件名（不包含后缀）
     * @return 新的文件路径
     */
    public static String changeFileName(String filepath, String newName) {
        int index = pathLastSeparatorIndex(filepath);
        String suffix = getFileSuffix(filepath, false);
        return filepath.substring(0, index + 1) + newName + suffix;
    }

    /**
     * 更改文件路径的文件后缀。
     *
     * @param filepath  原始文件路径
     * @param newSuffix 新的后缀名（可以以点开头也可以不以）
     * @return 新的文件路径
     */
    public static String changeFileSuffix(String filepath, String newSuffix) {
        String fixedNewSuffix = fixSuffix(newSuffix);
        int dotIndex = filepath.lastIndexOf(".");
        if (dotIndex == -1) { // Original file has no suffix
            return filepath + fixedNewSuffix;
        }
        return filepath.substring(0, dotIndex) + fixedNewSuffix;
    }

    /**
     * 从文件路径中获取目录路径，并可选地创建目录。
     *
     * @param filepath 文件路径
     * @param mkdir    如果为 true 则创建目录
     * @return 目录路径
     * @throws IOException 如果创建目录失败
     */
    public static String getDirPath(String filepath, boolean mkdir) throws IOException {
        Path path = Paths.get(filepath);
        Path parent = path.getParent();
        if (parent == null) { // e.g., "filename.txt" in current dir
            return "."; // Return current directory path
        }
        String dirPath = parent.toString();
        if (mkdir) {
            mkdirIfNotExists(dirPath);
        }
        return dirPath;
    }

    /**
     * 创建一个空文件。
     *
     * @param filepath 文件路径
     * @throws IOException 如果创建文件失败
     */
    public static void createFile(String filepath) throws IOException {
        Path path = Paths.get(filepath);
        if (Files.notExists(path)) {
            getDirPath(filepath, true); // Ensure parent directory exists
            Files.createFile(path);
        }
    }

    /**
     * 获取指定目录下的所有文件和目录的绝对路径，并按名称排序。
     *
     * @param absDirPath 绝对目录路径
     * @return 文件和目录的绝对路径列表
     * @throws IOException 如果读取目录失败
     */
    public static List<String> filesOfDir(String absDirPath) throws IOException {
        Path dirPath = Paths.get(fixFilepath(absDirPath));
        return Files.list(dirPath)
                .map(Path::toAbsolutePath)
                .map(Path::toString)
                .sorted()
                .collect(Collectors.toList());
    }

    /**
     * 遍历指定目录下的所有文件和目录，并对每个项执行接受器操作。
     *
     * @param absDirPath 绝对目录路径
     * @param acceptor   对每个文件或目录执行的操作 (Path path, String filename, int index)
     * @throws IOException 如果读取目录失败
     */
    public static void acceptFilesOfDir(String absDirPath, FileAcceptor acceptor) throws IOException {
        Path dirPath = Paths.get(fixFilepath(absDirPath));
        List<Path> paths = Files.list(dirPath)
                .sorted(Comparator.comparing(p -> p.getFileName().toString()))
                .collect(Collectors.toList());
        for (int i = 0; i < paths.size(); i++) {
            Path path = paths.get(i);
            acceptor.accept(path, path.getFileName().toString(), i);
        }
    }

    /**
     * 用于 acceptFilesOfDir 的函数式接口。
     */
    @FunctionalInterface
    public interface FileAcceptor {
        void accept(Path path, String filename, int index) throws IOException;
    }


    /**
     * 读取文本文件内容。
     *
     * @param filepath 文件路径
     * @return 文件内容字符串
     * @throws IOException 如果读取失败
     */
    public static String readText(String filepath) throws IOException {
        return new String(Files.readAllBytes(Paths.get(filepath)), StandardCharsets.UTF_8);
    }

    /**
     * 写入文本内容到文件。
     *
     * @param filepath 文件路径
     * @param content  要写入的内容
     * @throws IOException 如果写入失败
     */
    public static void writeText(String filepath, String content) throws IOException {
        getDirPath(filepath, true); // Ensure parent directory exists
        Files.write(Paths.get(filepath), content.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 保存 InputStream 的内容到文件。
     *
     * @param inputStream 输入流
     * @param filepath    文件路径
     * @throws IOException 如果保存失败
     */
    public static void saveInputStreamToFile(InputStream inputStream, String filepath) throws IOException {
        getDirPath(filepath, true); // Ensure parent directory exists
        try (OutputStream outputStream = Files.newOutputStream(Paths.get(filepath))) {
            byte[] buffer = new byte[8192]; // 8KB buffer
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
        }
    }

    /**
     * 获取文件夹下最新的文件名。
     *
     * @param folderPath 文件夹路径
     * @param returnPath 是否返回完整路径，否则返回文件名
     * @return 最新的文件路径或文件名
     * @throws IOException 如果读取目录失败
     */
    public static String getLatestFileName(String folderPath, boolean returnPath) throws IOException {
        Path dirPath = Paths.get(folderPath);
        if (!Files.isDirectory(dirPath)) {
            throw new IllegalArgumentException("Path is not a directory: " + folderPath);
        }

        return Files.list(dirPath)
                .max(Comparator.comparingLong(p -> {
                    try {
                        return Files.getLastModifiedTime(p).toMillis();
                    } catch (IOException e) {
                        throw new RuntimeException("Failed to get last modified time for " + p, e);
                    }
                }))
                .map(p -> returnPath ? p.toAbsolutePath().toString() : p.getFileName().toString())
                .orElse(null); // Return null if directory is empty
    }

    /**
     * 复制文件
     *
     * @param sourcePath      源文件路径
     * @param destinationPath 目标文件路径
     * @throws IOException
     */
    public static void copyFile(String sourcePath, String destinationPath) throws IOException {
        Path source = Paths.get(sourcePath);
        Path destination = Paths.get(destinationPath);
        mkdirIfNotExists(getDirPath(destinationPath, false)); // Ensure destination directory exists
        Files.copy(source, destination);
    }

    /**
     * 移动文件或目录
     *
     * @param sourcePath      源路径
     * @param destinationPath 目标路径
     * @throws IOException
     */
    public static void moveFile(String sourcePath, String destinationPath) throws IOException {
        Path source = Paths.get(sourcePath);
        Path destination = Paths.get(destinationPath);
        mkdirIfNotExists(getDirPath(destinationPath, false)); // Ensure destination directory exists
        Files.move(source, destination);
    }

    /**
     * 删除文件或空目录
     *
     * @param path 文件或目录路径
     * @throws IOException
     */
    public static void deleteFile(String path) throws IOException {
        Files.delete(Paths.get(path));
    }

    /**
     * 递归删除目录及其内容
     *
     * @param path 目录路径
     * @throws IOException
     */
    public static void deleteDirectoryRecursive(String path) throws IOException {
        Files.walk(Paths.get(path))
                .sorted(Comparator.reverseOrder()) // Delete contents before directory itself
                .map(Path::toFile)
                .forEach(File::delete);
    }
}